﻿using System.IO;
using System;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using QuantBox.QuantDo.Api;

namespace QuantBox.XApi
{
    public class QdpTradeClient
    {
        //对话流
        private const int QD_TSS_DIALOG = 1;
        //客户私有流
        private const int QD_TSS_PRIVATE = 2;
        //公共流
        private const int QD_TSS_PUBLIC = 3;
        //查询
        private const int QD_TSS_QUERY = 4;
        //用户私有流
        private const int QD_TSS_USER = 5;
        //MBL行情流
        private const int QD_TSS_MARKET = 6;

        private const string GuofuAuthCode = "b25873a136818afd00d36fcd3865e572";
        private const string OpenQuantAppId = "client_gftest_qdp";

        private static readonly Random Rand = new((int)DateTime.Now.Ticks);
        private int _requestId;
        private int _orderRef;
        internal Logger logger = LogManager.CreateNullLogger();

        private QdpQueryManager? _queryManager;
        private QdpDealProcessor? _processor;
        private StatusPublisher _publisher;

        internal IXSpi spi;
        internal QdpTraderApi? api;
        internal QdpRspUserLogin? qdpLoginInfo;
        internal QdpRspUserInvestor? qdpInvestor;
        internal string userCustom = string.Empty;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal int GetNextRequestId()
        {
            return Interlocked.Increment(ref _requestId);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private int GetNextOrderRef()
        {
            return Interlocked.Increment(ref _orderRef);
        }

        private static string GetFlowPath(ServerInfoField server, UserInfoField user)
        {
            var path = Path.Combine(Path.GetTempPath(), "XAPI", "T", $"{server.BrokerID}_{user.UserID}_{Rand.Next()}");
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            return path + Path.DirectorySeparatorChar;
        }
        private void SendError(ErrorField error)
        {
            Task.Run(() => spi.ProcessError(error));
        }

        internal void SendError(QdpRspInfo info, string source)
        {
            SendError(new ErrorField(info.ErrorId, info.ErrorId, info.ErrorMsg, source));
        }
        internal void SendError(int reason, string source)
        {
            SendError(new ErrorField(reason, reason, QdpConvert.GetReasonMsg(reason), source));
        }
        private void SendError(string msg)
        {
            SendError(new ErrorField(-1, -1, msg, nameof(QdpTradeClient)));
        }

        private void InitHandler()
        {
            // Order
            api!.RspHandlerList[QdpResponseType.OnRtnOrder] = ProcessDeal;
            api.RspHandlerList[QdpResponseType.OnRtnTrade] = ProcessDeal;
            api.RspHandlerList[QdpResponseType.OnErrRtnOrderAction] = ProcessDeal;
            api.RspHandlerList[QdpResponseType.OnErrRtnOrderInsert] = ProcessDeal;
            api.RspHandlerList[QdpResponseType.OnRspOrderAction] = ProcessDeal;
            api.RspHandlerList[QdpResponseType.OnRspOrderInsert] = ProcessDeal;
            // Query
            api.RspHandlerList[QdpResponseType.OnRspQryOrder] = ProcessQuery;
            api.RspHandlerList[QdpResponseType.OnRspQryTrade] = ProcessQuery;
            api.RspHandlerList[QdpResponseType.OnRspQryInstrument] = ProcessQuery;
            api.RspHandlerList[QdpResponseType.OnRspQryInvestorAccount] = ProcessQuery;
            api.RspHandlerList[QdpResponseType.OnRspQryInvestorPosition] = ProcessQuery;
            api.RspHandlerList[QdpResponseType.OnRtnInstrumentStatus] = OnRtnInstrumentStatus;
            api.RspHandlerList[QdpResponseType.OnRspQryDepthMarketData] = OnRspQryDepthMarketData;
            // Connection
            api.RspHandlerList[QdpResponseType.OnFrontConnected] = OnFrontConnected;
            api.RspHandlerList[QdpResponseType.OnFrontDisconnected] = OnFrontDisconnected;
            api.RspHandlerList[QdpResponseType.OnRspUserLogin] = OnRspUserLogin;
            api.RspHandlerList[QdpResponseType.OnRspAuthenticate] = OnRspAuthenticate;
            api.RspHandlerList[QdpResponseType.OnRspQryUserInvestor] = OnRspQryUserInvestor;
            api.RspHandlerList[QdpResponseType.OnRspError] = OnRspError;
        }

        #region Connect Response Handler

        private void DoQryUserInvestor()
        {
            var info = new QdpQryUserInvestor();
            info.BrokerId = Server!.BrokerID;
            info.UserId = User!.UserID;
            _publisher.Post(ConnectionStatus.Confirming);
            api!.ReqQryUserInvestor(info, GetNextRequestId());
        }

        private void OnRspQryUserInvestor(ref QdpResponse rsp)
        {
            qdpInvestor = rsp.Item1.AsRspUserInvestor;
            if (qdpInvestor != null && QdpConvert.CheckRspInfo(rsp.Item2))
            {
                ConnectDone();
            }
            else
            {
                SendError(rsp.Item2, nameof(OnRspQryUserInvestor));
                _publisher.Post(ConnectionStatus.Disconnected);
            }
        }

        private void ConnectDone()
        {
            ReqReady();
            Connected = true;
            _publisher.Post(ConnectionStatus.Confirmed);
            _publisher.Post(ConnectionStatus.Done, UserLogin!);
        }

        private void ReqReady()
        {
            var flow = new QdpFlowStatus();
            flow.SequenceSeries = QD_TSS_PRIVATE;
            flow.BReady = 1;
            api!.ReqReady(flow, GetNextRequestId());
            flow.SequenceSeries = QD_TSS_PUBLIC;
            flow.BReady = 1;
            api.ReqReady(flow, GetNextRequestId());
        }

        private void DoLogin()
        {
            var info = new QdpReqUserLogin();
            info.BrokerId = Server!.BrokerID;
            info.UserProductInfo = Server.UserProductInfo;
            info.UserId = User!.UserID;
            info.Password = User.Password;
            if (!Connected)
            {
                _publisher.Post(ConnectionStatus.Logining);
            }
            else
            {
                logger.Debug($"Trader({User.UserID}) login");
            }

            api!.ReqUserLogin(info, GetNextRequestId());
        }

        private void DoAuthenticate()
        {
            var info = new QdpAuthenticate();
            info.BrokerId = Server!.BrokerID;
            info.UserId = User!.UserID;
            info.UserProductInfo = Server.UserProductInfo;
            info.AuthCode = Server.AuthCode;
            info.AppId = Server.ExtInfoChar128;

            if (!Connected)
            {
                _publisher.Post(ConnectionStatus.Authorizing);
            }
            else
            {
                logger.Debug($"Trader({User.UserID}) authorizing");
            }

            api!.ReqAuthenticate(info, GetNextRequestId());
        }

        private void OnRspError(ref QdpResponse rsp)
        {
            _queryManager!.Post(QueryType.ReqError, rsp);
            SendError(rsp.Item1.AsRspInfo, nameof(OnRspError));
        }

        private void OnRspUserLogin(ref QdpResponse rsp)
        {
            qdpLoginInfo = rsp.Item1.AsRspUserLogin;
            if (qdpLoginInfo != null && QdpConvert.CheckRspInfo(rsp.Item2))
            {
                if (!Connected)
                {
                    UserLogin = new RspUserLoginField();
                    UserLogin.TradingDay = QdpConvert.GetDate(qdpLoginInfo.TradingDay);
                    UserLogin.LoginTime = QdpConvert.GetTime(qdpLoginInfo.LoginTime);
                    UserLogin.UserID = qdpLoginInfo.UserId;
                    UserLogin.SessionID = $"{qdpLoginInfo.FrontId}:{qdpLoginInfo.SessionId}";
                    UserLogin.Text = qdpLoginInfo.MaxOrderLocalId.ToString();
                    if (!int.TryParse(UserLogin.Text, out _orderRef))
                    {
                        int.TryParse(UserLogin.Text, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _orderRef);
                    }
                    _publisher.Post(ConnectionStatus.Logined, UserLogin);
                    if (qdpInvestor != null)
                    {
                        ConnectDone();
                    }
                    else
                    {
                        DoQryUserInvestor();
                    }
                }
                else
                {
                    logger.Debug($"Trader login success");
                }
            }
            else
            {
                SendError(rsp.Item2, nameof(OnRspUserLogin));
                _publisher.Post(ConnectionStatus.Disconnected);
            }
        }

        private void OnFrontConnected(ref QdpResponse rsp)
        {
            logger.Debug($"Trader connected");

            if (!Connected)
            {
                _publisher.Post(ConnectionStatus.Connected);
            }

            if (string.IsNullOrEmpty(Server!.AuthCode))
            {
                DoLogin();
            }
            else
            {
                DoAuthenticate();
            }
        }

        private void OnFrontDisconnected(ref QdpResponse rsp)
        {
            var reason = rsp.Item1.AsInt;
            if (reason != 0)
            {
                SendError(reason, $"{User!.UserID}");
            }
        }

        private void OnRspAuthenticate(ref QdpResponse rsp)
        {
            var data = rsp.Item1.AsRtnAuthenticate;
            if (data != null && QdpConvert.CheckRspInfo(rsp.Item2))
            {
                if (!Connected)
                {
                    _publisher.Post(ConnectionStatus.Authorized);
                }
                else
                {
                    logger.Debug($"Trader authorized");
                }

                DoLogin();
            }
            else
            {
                SendError(rsp.Item2, nameof(OnRspAuthenticate));
                _publisher.Post(ConnectionStatus.Disconnected);
            }
        }
        #endregion

        #region Query Response Handler

        private void ProcessQuery(ref QdpResponse rsp)
        {
            switch (rsp.TypeId)
            {
                case QdpResponseType.OnRspQryOrder:
                    _queryManager?.Post(QueryType.ReqQryOrder, rsp);
                    break;
                case QdpResponseType.OnRspQryTrade:
                    _queryManager?.Post(QueryType.ReqQryTrade, rsp);
                    break;
                case QdpResponseType.OnRspQryInvestorAccount:
                    _queryManager?.Post(QueryType.ReqQryTradingAccount, rsp);
                    break;
                case QdpResponseType.OnRspQryInvestorPosition:
                    _queryManager?.Post(QueryType.ReqQryInvestorPosition, rsp);
                    break;
                case QdpResponseType.OnRspQryInstrument:
                    _queryManager?.Post(QueryType.ReqQryInstrument, rsp);
                    break;
            }
        }

        private void OnRtnInstrumentStatus(ref QdpResponse rsp)
        {
            var status = rsp.Item1.AsInstrumentStatus;
            var field = QdpConvert.GetInstrumentStatus(status);
            spi.ProcessRtnInstrumentStatus(field);
        }

        private void OnRspQryDepthMarketData(ref QdpResponse rsp)
        {

        }

        #endregion

        #region Order Response Handler

        private void ProcessDeal(ref QdpResponse rsp)
        {
            _processor!.Post(rsp);
        }

        #endregion

        public QdpTradeClient(IXSpi spi)
        {
            this.spi = spi;
            _publisher = new StatusPublisher(spi);
        }

        public void RegisterSpi(IXSpi spi)
        {
            this.spi = spi;
            _publisher = new StatusPublisher(spi);
        }

        public bool Connected { get; private set; }
        public ServerInfoField? Server { get; private set; }
        public UserInfoField? User { get; private set; }
        public RspUserLoginField? UserLogin { get; private set; }

        private bool CheckSettings(ServerInfoField server, UserInfoField user)
        {
            if (string.IsNullOrEmpty(server.Address))
            {
                SendError("没有设置 Address");
                _publisher.Post(ConnectionStatus.Disconnected);
                return false;
            }
            if (string.IsNullOrEmpty(server.BrokerID))
            {
                SendError("没有设置 BrokerID");
                _publisher.Post(ConnectionStatus.Disconnected);
                return false;
            }
            if (string.IsNullOrEmpty(user.UserID) || string.IsNullOrEmpty(user.Password))
            {
                SendError("没有设置用户名和密码");
                _publisher.Post(ConnectionStatus.Disconnected);
                return false;
            }
            #region 设置国富认证码
            //连接实盘系统
            if (string.IsNullOrEmpty(server.ExtInfoChar128))
            {
                server.AuthCode = GuofuAuthCode;
                server.ExtInfoChar128 = OpenQuantAppId;
            }
            #endregion
            return true;
        }

        public void Init(ServerInfoField server, UserInfoField user)
        {
            if (api != null)
            {
                return;
            }
            if (!CheckSettings(server, user))
            {
                return;
            }

            userCustom = server.ConfigPath;
            if (string.IsNullOrEmpty(userCustom))
            {
                logger = LogManager.GetLogger($"Qdp.{user.UserID}");
            }
            else
            {
                logger = LogManager.GetLogger($"Qdp.{user.UserID}.{userCustom}");
            }
            _queryManager = new QdpQueryManager(this);
            _processor = new QdpDealProcessor(this);
            User = user;
            Server = server;

            api = new QdpTraderApi(GetFlowPath(server, user));
            InitHandler();
            var items = server.Address.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (var item in items)
            {
                api.RegisterFront(item);
            }
            api.SubscribePrivateTopic(QdpConvert.GetQdpResumeType(server.PrivateTopicResumeType));
            api.SubscribePublicTopic(QdpConvert.GetQdpResumeType(server.PublicTopicResumeType));
            api.SubscribeUserTopic(QdpConvert.GetQdpResumeType(server.UserTopicResumeType));

            _publisher.Post(ConnectionStatus.Connecting);
            api.Init();
        }

        public void Release()
        {
            if (api != null)
            {
                _publisher.Post(ConnectionStatus.Releasing);
                _queryManager!.Close();
                _queryManager = null;
                Connected = false;
                api.Release();
                api = null;
                _processor!.Close();
                _processor = null;
                _publisher.Post(ConnectionStatus.Disconnected);
            }
        }

        public void Query(QueryType type, ReqQueryField field)
        {
            if (Connected)
            {
                _queryManager!.Post(type, field);
            }
        }

        public string SendOrder(OrderField order)
        {
            if (!Connected)
            {
                return string.Empty;
            }

            if (string.IsNullOrEmpty(order.LocalID))
            {
                order.LocalID = GetNextOrderRef().ToString();
            }
            _processor!.Post(order);
            return order.ID;
        }

        public void CancelOrder(string id)
        {
            if (Connected)
            {
                _processor!.Post(id);
            }
        }
    }
}